package com.simple.business.service.sms.impl;

import com.alibaba.fastjson2.JSONObject;
import com.github.zuihou.base.R;
import com.simple.business.config.utils.SimpleUtil;
import com.simple.business.domain.properties.VerificationCodeProperties;
import com.simple.business.domain.dto.sms.RedisVerificationCode;
import com.simple.business.domain.dto.sms.SmsDTO;
import com.simple.business.domain.dto.sms.SmsResult;
import com.simple.business.domain.dto.sms.VerificationCodeDTO;
import com.simple.business.service.sms.SmsService;
import com.simple.business.service.sms.VerificationCodeService;
import com.simple.common.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Date;
import java.util.Objects;

@Slf4j
@Service
public class VerificationCodeServiceImpl implements VerificationCodeService {
    private static final String REDIS_SEND_LIMIT_KEY = "sendLimit_";
    private static final String REDIS_VERIFY_CODE = "VERIFY_CODE_INFO_";

    private static final String EMAIL_TEMPLATE = "尊敬的用户，您的验证码：%s，请在5分钟内完成验证，如非本人操作请忽略，感谢您的支持!";
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private  SmsService smsService;
    @Resource
    private SimpleUtil simpleUtil;
    @Resource
    private VerificationCodeProperties verificationCodeProperties;
    @Value("${spring.mail.username}")
    String emailUserName;

    /**
     * 验证码检查
     * @param userName 手机号或者邮箱号
     * @param code 验证码
     * @param checkUser 是否检查发送者
     * @param userId 当前进行验证的用户id
     * @param useOnce 验证后删除，只用一次
     * @return
     */
    @Override
    public R<Boolean> codeCheck(String userName, String code,  boolean checkUser, Long userId , boolean useOnce) {
        String key = REDIS_VERIFY_CODE + userName;
        Object obj = redisTemplate.opsForValue().get(key);

        RedisVerificationCode redisVerificationCode = JSONObject.parseObject(JSONObject.toJSONString(obj), RedisVerificationCode.class);

        //不能直接转成RedisVerificationCode
        // if (!(obj instanceof RedisVerificationCode)) {
        //     return R.fail("验证码已过期或失效，请重新获取");
        // }
        // RedisVerificationCode redisVerificationCode = (RedisVerificationCode) obj;
        if (checkUser) {
            if (redisVerificationCode.getSenderId() == null || !redisVerificationCode.getSenderId().equals(userId)) {
                return R.fail("验证码主体缺失，请重新获取");
            }
        }
        if (StringUtils.isBlank(redisVerificationCode.getCode()) ||
                redisVerificationCode.getCreateTime().getTime() +
                        verificationCodeProperties.getCodeExpireTime() * 60 * 1000 < System.currentTimeMillis()) {
            return R.fail("当前验证码已过期或失效，请重新获取");
        }
        if (redisVerificationCode.getCode().equals(code)) {
            if(useOnce){
                // 成功验证则清除验证码
                redisVerificationCode.setCode("");
                // 成功后重置可发送次数
                redisVerificationCode.setCountByDay(0);
                redisTemplate.opsForValue().set(key, redisVerificationCode, Duration.ofHours(24));
            }
            return R.success();
        } else {
            redisVerificationCode.setFailCnt(redisVerificationCode.getFailCnt() + 1);
            R result = R.fail("验证码错误");
            if (redisVerificationCode.getFailCnt() > verificationCodeProperties.getCodeCheckLimitCnt()) {
                // 失败超过最大次数则验证码失效
                redisVerificationCode.setCode("");
                result.setMsg(result.getMsg() + "，错误次数过多，验证码已失效，请重新获取。");
            }
            redisTemplate.opsForValue().set(key, redisVerificationCode, Duration.ofHours(24));
            return result;
        }
    }

    @Override
    public R<String> sendSmsCode(String mobile) {
        Long userId = SecurityUtils.getUserId();
        R<String> limitCheck = limitCheck(mobile);
        if (limitCheck.getIsError()) {
            return limitCheck;
        }
        String code = RandomStringUtils.randomNumeric(6);
        R<String> saveResult = saveCode(mobile, code, userId);
        if (saveResult.getIsError()) {
            return saveResult;
        }
        if (StringUtils.isNotBlank(System.getenv("testenv"))){
            return R.success(code);
        }

        if (true) {
            return R.success(code);
        }

        SmsDTO smsDTO = SmsDTO.builder()
                .mobile(mobile)
                .templateParams(String.format(AliyunSmsServiceImpl.DEFAULT_TEMPLATE_PARAM, code))
                .build();
        SmsResult send = smsService.send(smsDTO);
        if (Boolean.TRUE.equals(send.getStatus())) {
            return R.success("发送成功");
        }
        return R.fail("发送失败");
    }

    @Override
    public R<String> sendEmailCode(String email) {
        Long userId = SecurityUtils.getUserId();

        R<String> limitCheck = limitCheck(email);
        if (limitCheck.getIsError()) {
            return limitCheck;
        }
        String code = RandomStringUtils.randomNumeric(6);
        R<String> saveResult = saveCode(email, code, userId);
        if (saveResult.getIsError()) {
            return saveResult;
        }
        try {
            SimpleMailMessage mailMessage = new SimpleMailMessage();
            mailMessage.setTo(email);
            mailMessage.setFrom(emailUserName);
            mailMessage.setSubject(verificationCodeProperties.getTitle());
            mailMessage.setText(String.format(EMAIL_TEMPLATE, code));
            // javaMailSender.send(mailMessage);
        } catch (Exception exception) {
            log.error("发送邮件失败", exception);
            return R.fail("发送失败，请稍后重试");
        }
        return R.success("邮件发送成功");
    }


    @Override
    public void deleteCode(String account) {
        redisTemplate.delete(REDIS_VERIFY_CODE + account);
    }

    /**
     * 限制次数
     * @param account
     * @return
     */
    private R<String> limitCheck(String account) {
        String key = REDIS_SEND_LIMIT_KEY + account;
        // N分钟只能发送一次
        ValueOperations<String, Object> redis = redisTemplate.opsForValue();

        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            return R.fail("发送过快，请" + verificationCodeProperties.getSendLimitTime() + "分钟后再进行操作");
        }
        Long increment = redis.increment(key);
        if (increment > 1) {
            return R.fail("发送过快，请" + verificationCodeProperties.getSendLimitTime() + "分钟后再进行操作");
        }
        redisTemplate.expire(key, Duration.ofMinutes(verificationCodeProperties.getSendLimitTime()));
        return R.success("");
    }

    /**
     * 保存验证码
     * @param account
     * @param code
     * @param userId
     * @return
     */
    private R<String> saveCode(String account, String code, Long userId) {
        String key = REDIS_VERIFY_CODE + account;
        int countByDay = 0;
        Object oldObj = redisTemplate.opsForValue().get(key);

        RedisVerificationCode redisVerificationCode = null;
        try {
            redisVerificationCode = JSONObject.parseObject(JSONObject.toJSONString(oldObj), RedisVerificationCode.class);
        } catch (Exception e) {
            log.error("转化异常: " + e.getMessage());
        }

        if (Objects.nonNull(redisVerificationCode)) {
            Date createTime = redisVerificationCode.getCreateTime();
            if (createTime.getTime() + 24 * 60 * 60 * 1000 > System.currentTimeMillis()) {
                countByDay = redisVerificationCode.getCountByDay() + 1;
            }
        }
        RedisVerificationCode build = RedisVerificationCode
                .builder()
                .createTime(new Date())
                .failCnt(0)
                .code(code)
                .countByDay(countByDay)
                .senderId(userId)
                .build();
        // 一天只能发送N次
        if (build.getCountByDay() > verificationCodeProperties.getSendLimitCnt()) {
            return R.fail("已超过当日最大发送次数，请24小时后再次使用");
        }
        redisTemplate.opsForValue().set(key, build, Duration.ofHours(24));
        return R.success("ok");
    }
}
